#include "qrender/q3ds_renderer.h"

#include <GL/gl.h>

#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QOpenGLContext>
#include <QOpenGLFunctions>

#include <QtMath>
#include <QFile>

#include <qrender/q3ds_model.h>
#include <qrender/camera.h>

Q3dsRenderer::Q3dsRenderer() : Renderer()
{
    // I5 默认 DH 参数
    dh_["a"] = std::vector<double>{ 0, 0, 0.408, 0.376, 0, 0 };
    dh_["alpha"] =
        std::vector<double>{ 0, -M_PI / 2., M_PI, M_PI, -M_PI / 2., M_PI / 2. };
    dh_["d"] = std::vector<double>{ 0.122, 0.1215, 0, 0, 0.1025, 0.094 };
    dh_["theta"] = std::vector<double>{ M_PI, -M_PI / 2., 0, -M_PI / 2., 0, 0 };

    q3ds_model_.reset(new Q3dsModel());
}

Q3dsRenderer::~Q3dsRenderer()
{
    Q3dsRenderer::release();
}

bool Q3dsRenderer::reload(
    const QStringList &dirs, const QString &robot_type,
    const std::unordered_map<std::string, std::vector<double>> &dh)
{
    if (dirs_ == dirs && robot_type_ == robot_type && dh_ == dh) {
        return true;
    }

    dirs_ = dirs;
    robot_type_ = robot_type;
    dh_ = dh;

    return q3ds_model_->reload(dirs, robot_type, dh);
}

void Q3dsRenderer::setJointPositions(const std::vector<double> &joint_positions)
{
    if (joint_positions_ == joint_positions) {
        return;
    }

    joint_positions_ = joint_positions;

    q3ds_model_->setJointPositions(joint_positions);
}

void Q3dsRenderer::setFilter(QVector4D color)
{
    if (filter_color_ == color) {
        return;
    }

    filter_color_ = color;
}

std::vector<double> Q3dsRenderer::getJointPositions() const
{
    return joint_positions_;
}

QMatrix4x4 Q3dsRenderer::getEndWaypointMatrix()
{
    return end_waypoint_matrix_;
}

void Q3dsRenderer::create()
{
    q3ds_model_->reload(dirs_, robot_type_, dh_);
    q3ds_model_->setJointPositions(joint_positions_);

    const char *vsrc = R"(
#version 330

// 输入既有顶点又有法向量
in vec3 v_pos;
in vec3 v_normal;
in vec3 v_diffuse;

// 向片元着色器输出世界空间的顶点位置以及法向量
out vec3 frag_pos;
out vec3 normal;
out vec3 material_diffuse;

uniform mat4 model_matrix;
uniform mat4 view_matrix;
uniform mat4 projection_matrix;
uniform mat3 normal_matrix;

void main()
{
    // 视角空间，投影矩阵、观察矩阵、模型矩阵
    gl_Position = projection_matrix * view_matrix * model_matrix * vec4(v_pos, 1.0);

    // 这个是世界空间顶点位置，只做了模型转换(这是在世界空间计算冯氏光照)
    frag_pos = vec3(model_matrix * vec4(v_pos, 1.0));

    // 法向量也需要一个复杂转换：法线矩阵 与 法向量相乘。
    // 使用Inverse(逆矩阵)和Transpose(转置矩阵)函数自己生成这个法线矩阵
    normal = normalize(normal_matrix * v_normal);

    material_diffuse = v_diffuse;
}

)";

    const char *fsrc = R"(
#version 330 core

// 材质
struct Material {
    vec3 ambient;    // 环境光
    vec3 diffuse;    // 漫反射
    vec3 specular;   // 镜面光
    float shininess; // 反光度
};

// 光照
struct Light {
    vec3 position;  // 位置
    vec3 ambient;   // 环境光
    vec3 diffuse;   // 漫反射
    vec3 specular;  // 镜面光
};

uniform Material material;
uniform Light light;
uniform vec3 view_pos;
uniform vec4 filter_color;

in vec3 frag_pos; // 顶点位置
in vec3 normal;   // 法向量
in vec3 material_diffuse;

void main()
{
    // 环境光
    vec3 ambient = light.ambient * material.ambient;

    // 漫反射
    vec3 light_dir = normalize(light.position - frag_pos);
    float diff = max(dot(normal, light_dir), 0.0);
    vec3 diffuse = light.diffuse * (diff * material.diffuse);

    // 镜面反射
//    vec3 view_dir = normalize(view_pos - frag_pos);
//    vec3 reflect_dir = reflect(-light_dir, normal);
//    float spec = pow(max(dot(view_dir, reflect_dir), 0.0), material.shininess);
//    vec3 specular = light.specular * (spec * material.specular);

    // NOTE: 使用 specular 会导致两侧变黑, 可能是光源问题
    vec3 res = ambient + diffuse/* + specular*/;

    if(filter_color.a != 0){
        gl_FragColor = filter_color;
    } else {
        gl_FragColor = vec4(res, 1.0);
    }
}

)";

    // 创建着色器
    program_.reset(new QOpenGLShaderProgram());
    program_->addCacheableShaderFromSourceCode(QOpenGLShader::Vertex,
                                               vsrc); // 顶点着色器
    program_->addCacheableShaderFromSourceCode(QOpenGLShader::Fragment,
                                               fsrc); // 片段着色器
    program_->link();
    program_->bind();

    program_->setUniformValue("view_matrix", QMatrix4x4());
    program_->setUniformValue("projection_matrix", QMatrix4x4());
    // 光照
    program_->setUniformValue("light.position", QVector3D(1e5, 1e5, 1e5));
    program_->setUniformValue("light.ambient", QVector3D(1.0f, 1.0f, 1.0f));
    program_->setUniformValue("light.diffuse", QVector3D(1.0f, 1.0f, 1.0f));
    program_->setUniformValue("light.specular", QVector3D(0.05f, 0.05f, 0.05f));
    // 材质, 只有 diffuse 有变化,其他都是固定的
    program_->setUniformValue("material.ambient", QVector3D(0.2, 0.2, 0.2));
    program_->setUniformValue("material.specular", QVector3D(1.0, 1.0, 1.0));
    program_->setUniformValue("material.shininess", 2);
    // 滤镜

    vao_.reset(new QOpenGLVertexArrayObject());
    vao_->create();

    // 顶点数据
    generateMeshVertices(q3ds_model_->getMeshs());

    vbo_.reset(new QOpenGLBuffer());
    vbo_->create();
    vbo_->bind();

    {
        QOpenGLVertexArrayObject::Binder vao_binder(vao_.get());

        //告知OPenGL如何解析数据, 数据解析格式
        GLuint vPosition = program_->attributeLocation("v_pos");
        program_->setAttributeBuffer(vPosition, GL_FLOAT, 0, 3,
                                     6 * sizeof(GLfloat));
        program_->enableAttributeArray(vPosition);

        GLuint vNormal = program_->attributeLocation("v_normal");
        program_->setAttributeBuffer(vNormal, GL_FLOAT, 3 * sizeof(GLfloat), 3,
                                     6 * sizeof(GLfloat));
        program_->enableAttributeArray(vNormal);
    }

    program_->release();

    vbo_->release();
}

void Q3dsRenderer::render(const CameraInfo &camera)
{
    bool meshs_changed = q3ds_model_->meshsChanged(); // 一定在 meshs 前调用
    auto meshs = q3ds_model_->getMeshs();
    auto f = QOpenGLContext::currentContext()->functions();
    f->glEnable(GL_DEPTH_TEST);
    f->glEnable(GL_POLYGON_SMOOTH);
    f->glEnable(GL_CULL_FACE); // NOTE: 大幅减少白斑, 但 scope 里是禁用
    program_->bind();
    vbo_->bind();

    if (camera.valid) {
        program_->setUniformValue("view_matrix", camera.view_matrix);
        program_->setUniformValue("projection_matrix",
                                  camera.projection_matrix);
        program_->setUniformValue("view_pos", camera.view_position);

        // 光源随着视角走,防止某一面是黑的
        program_->setUniformValue("light.position", camera.view_position);
    }

    // 如果顶点数据发生变化
    if (meshs_changed) {
        generateMeshVertices(meshs);
    }

    // 更新末端模型矩阵
    if (!meshs.isEmpty()) {
        end_waypoint_matrix_ = meshs.back()->dh_matrix;
    }

    program_->setUniformValue("filter_color", filter_color_);
    for (int i = 0; i < meshs.size(); i++) {
        program_->setUniformValue("model_matrix", meshs[i]->dh_matrix);
        program_->setUniformValue("normal_matrix",
                                  (meshs[i]->dh_matrix)
                                      .inverted()
                                      .transposed()
                                      .toGenericMatrix<3, 3>());

        auto vbo_data = vbo_datas_[i];
        for (auto &vbo_d : vbo_data) {
            vbo_->allocate(vbo_d.second.data(),
                           sizeof(GLfloat) * vbo_d.second.size());
            QOpenGLVertexArrayObject::Binder vao_binder(vao_.get());
            f->glDrawArrays(GL_TRIANGLES, 0, vbo_d.second.size() / 6);

            program_->setUniformValue("material.diffuse", vbo_d.first);
        }
    }

    vbo_->release();
    program_->release();
}

void Q3dsRenderer::release()
{
    vbo_.reset();
    vao_.reset();
    program_.reset();
}

void Q3dsRenderer::generateMeshVertices(QVector<Q3dsMesh *> meshs)
{
    static QVector4D last_diffuse_color;

    vbo_datas_.clear();

    for (auto &mesh : meshs) {
        QList<QPair<QVector3D, QVector<GLfloat>>> vbo_d;
        QVector<GLfloat> v;
        int v_i = 0;
        v.resize(mesh->faces.size() * 3 * 3 * 2);

        for (auto &face : mesh->faces) {
            if (last_diffuse_color != face.diffuse) {
                vbo_d << qMakePair(face.diffuse.toVector3D(), v.mid(0, v_i));
                v_i = 0;

                last_diffuse_color = face.diffuse;
            }

            for (int i = 0; i < face.points.size(); i++) {
                // 顶点
                for (int j = 0; j < 3; j++) {
                    v[v_i++] = face.points[i][j];
                }

                // 法向量
                for (int j = 0; j < 3; j++) {
                    v[v_i++] = face.normal[j];
                }
            }
        }

        vbo_d << qMakePair(mesh->faces.back().diffuse.toVector3D(),
                           v.mid(0, v_i));
        vbo_datas_ << vbo_d;
    }
}
